package com.basic.common.utils;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.IntStream;

/**
 * ClassName: UploadUtil
 * Package: com.basic.common.utils
 * Description:
 *      Uploading Util
 * @Author: Night
 * @Create: 2023/12/12 - 9:11
 * @Version: 1.0
 */
@Component
public class UploadUtil {
    // 上传路径
    //spring配置文件中的属性在别处也可调用，此处声明为静态的意义不明(见名知意?)
    public static String linuxPath;
    private static final int EXPECTED_CHUNKS_COUNT = 20;
    private final Map<String, FileUploadProgress> uploadProgressMap = new HashMap<>();


    /**
     *  使用@Value初始化静态成员变量
     *  并且创建对应文件夹
     * @param linuxPath
     */
    @Value("${linuxPath}")
    public void setLinuxPath(String linuxPath) {
        UploadUtil.linuxPath = linuxPath;
        if (!new File(linuxPath).isDirectory()) {
            new File(linuxPath + "video/").mkdirs();
            new File(linuxPath + "accessory/").mkdir();
            new File(linuxPath + "blueprint/").mkdir();
            new File(linuxPath + "model/").mkdir();
        }
    }

    static String formattedDate = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());

    /**
     *
     * @param accessoryData
     * @param originalFileName
     * @return
     */
    public static String accessoryFile(String accessoryData, String originalFileName) {
        return decodeToBinary(accessoryData,linuxPath+"accessory/",originalFileName);
    }
    public static String accessoryFile(MultipartFile accessoryData){
        return upLoadByBinary(accessoryData,linuxPath+"accessory/");
    }

    /**
     * 上传泵站多媒体文件
     * @param videoData String
     * @return 返回上传路径
     */
    public static String videoFile(String videoData, String originalFileName){
        return decodeToBinary(videoData,linuxPath + "video/",originalFileName);
    }

    public static String videoFile(MultipartFile videoData){
        return upLoadByBinary(videoData,linuxPath + "video/");
    }

    /**
     * 上传泵站图纸信息文件
     * @param drawData String
     * @return 返回上传路径
     */
    public static String drawFile(String drawData,String originalFileName){
        return decodeToBinary(drawData,linuxPath + "blueprint/",originalFileName);
    }
    public static String drawFile(MultipartFile drawData){
        return upLoadByBinary(drawData, linuxPath + "blueprint/");
    }

    /**
     * 上传泵站三维模型信息文件
     * @param modelData String
     * @return 返回上传路径
     */
    public static String modelFile(String modelData, String originalFileName){
        return decodeToBinary(modelData,linuxPath + "model/",originalFileName);
    }
    public static String modelFile(MultipartFile modelData){
        return upLoadByBinary(modelData, linuxPath + "model/");
    }

    /**
     * 根据路径以及文件返回的文件base64字符串，将其存储并返回存储路径
     * @param base64Data Base64编码数据
     * @param filePath String
     * @return 返回上传路径
     */
    private static String decodeToBinary(String base64Data, String filePath, String originalFileName){
        //System.out.println("directory.getAbsolutePath()"+filePath.toString());
        // 生成唯一的随机文件名,可修改。
        String randomFileName = UUID.randomUUID() + "_" + formattedDate + originalFileName.substring(originalFileName.lastIndexOf("."));
        // 拼接存储路径
        String finalPath = filePath + randomFileName;
        try (FileOutputStream fos = new FileOutputStream(finalPath)){
            //解码
            byte[] decodeData = Base64.getDecoder().decode(base64Data);
            fos.write(decodeData);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return finalPath;
    }

    /**
     * 直接上传文件
     * @param fileData 文件
     * @param filePath 文件存储目录
     * @return 文件存储路径
     */
    public static String upLoadByBinary(MultipartFile fileData, String filePath){
        // 生成唯一的随机文件名,可修改。
        String randomFileName = UUID.randomUUID() + "_" + formattedDate + Objects.requireNonNull(fileData.getOriginalFilename()).substring(fileData.getOriginalFilename().lastIndexOf("."));
        // 拼接存储路径
        String finalPath = filePath + randomFileName;
        try (FileOutputStream fos = new FileOutputStream(finalPath)){
            byte[] bytes = fileData.getBytes();
            fos.write(bytes);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return finalPath;
    }

    /**
     * 分片上传
     * @param fileChunk 上传工具内部类
     * @param uploadDir 存储路径
     * @throws IOException
     */
    public void uploadChunk(FileChunk fileChunk, String uploadDir) throws IOException {
        //从 uploadProgressMap 中获取与 fileId 关联的 FileUploadProgress 实例。
        //如果找不到，它将自动使用 fileId 创建一个新的 FileUploadProgress 实例，将其添加到 Map 中，并返回这个新实例
        FileUploadProgress progress =
                uploadProgressMap.computeIfAbsent(fileChunk.getFileId(), id -> new FileUploadProgress());

        //验证分片MD5
        String md5Code = MD5Util.getMD5Code(fileChunk.getData());
        if (!md5Code.equals(fileChunk.getMD5())){
            throw new IOException("MD5 checksum mismatch for chunk: " + fileChunk.getChunkIndex());
        }

        // 创建存储分片数据的文件
        File chunkFile = new File(uploadDir, fileChunk.getFileId() + "_chunk_" + fileChunk.getChunkIndex());
        // 将FileChunk分片数据存入文件中
        FileCopyUtils.copy(fileChunk.getData(), chunkFile);
        // 将分片索引存入progress中
        progress.addUploadedChunk(fileChunk.getChunkIndex());

        // 检查上传是否完成
        if (isUploadComplete(progress)) {
            mergeFile(progress, uploadDir, fileChunk.getFileId());
            uploadProgressMap.remove(fileChunk.getFileId()); // 清除进度记录
        }
    }

    /**
     *
     * @param progress
     * @return
     */
    private boolean isUploadComplete(FileUploadProgress progress) {
        // 这里简化处理，实际应根据分片数量进行判断
        return progress.getUploadedChunksCount() == EXPECTED_CHUNKS_COUNT;
    }

    private boolean isUploadComplete(FileUploadProgress progress, int expectedChunksCount, long fileSize) {
        // 首先，确认上传进度对象不为空
        if (progress == null) {
            return false;
        }

        // 获取已上传分片的索引集合
        Set<Integer> uploadedChunks = progress.getUploadedChunks();
        // 如果预期的分片数量与已上传的分片数量不一致，说明还有分片未上传
        if (uploadedChunks.size() != expectedChunksCount) {
            return false;
        }

        // 确认所有预期的分片都已上传
        // 假设分片索引从0开始，因此最后一个分片索引为 expectedChunksCount - 1
        for (int i = 0; i < expectedChunksCount; i++) {
            if (!uploadedChunks.contains(i)) {
                // 如果任何一个预期的分片索引不在已上传的集合中，说明有分片丢失
                return false;
            }
        }

        // 上传完毕且所有分片均已上传
        return true;
    }


    /**
     *
     * @param progress
     * @param uploadDir
     * @param fileId
     * @throws IOException
     */
    private void mergeFile(FileUploadProgress progress, String uploadDir, String fileId) throws IOException {
        List<Integer> sortedChunks = new ArrayList<>(progress.getUploadedChunks());
        Collections.sort(sortedChunks);
        try (FileOutputStream mergedFile = new FileOutputStream(new File(uploadDir, fileId))) {
            for (Integer chunkIndex : sortedChunks) {
                File chunkFile = new File(uploadDir, fileId + "_chunk_" + chunkIndex);
                Files.copy(chunkFile.toPath(), mergedFile);
                chunkFile.delete(); // 合并后删除分片文件
            }
        }
    }

    /**
     *
     * @param fileId
     * @return
     */
    public Set<Integer> getMissingChunks(String fileId) {
        FileUploadProgress progress = uploadProgressMap.get(fileId);
        if (progress == null) {
            return Collections.emptySet();
        }

        Set<Integer> uploadedChunks = progress.getUploadedChunks();
        Set<Integer> missingChunks = new HashSet<>();
        // 假定分片索引是从0开始到EXPECTED_CHUNKS_COUNT - 1
        IntStream.range(0, EXPECTED_CHUNKS_COUNT).forEach(index -> {
            if (!uploadedChunks.contains(index)) {
                missingChunks.add(index);
            }
        });
        return missingChunks;
    }


    /**
     * 用于封装文件数据的内部实体类
     */
    public static class UploadFileVo{
        private String originFileName;
        private String fileData;

        public UploadFileVo() {}

        public UploadFileVo(String originFileName, String fileData) {
            this.originFileName = originFileName;
            this.fileData = fileData;
        }

        public String getOriginFileName() {
            return originFileName;
        }

        public void setOriginFileName(String originFileName) {
            this.originFileName = originFileName;
        }

        public String getFileData() {
            return fileData;
        }

        public void setFileData(String fileData) {
            this.fileData = fileData;
        }
    }

    /**
     * 用于封装文件分片的内部实体类
     */
    public static class FileChunk{
        // 文件的唯一标识符
        String fileId;
        // 该分片的MD5值
        String MD5;
        // 该分片的索引值
        int chunkIndex;
        // 该分片的数据
        byte[] data;

        public FileChunk() {}

        public FileChunk(String fileId, String MD5, int chunkIndex, byte[] data) {
            this.fileId = fileId;
            this.MD5 = MD5;
            this.chunkIndex = chunkIndex;
            this.data = data;
        }

        public String getFileId() {
            return fileId;
        }

        public void setFileId(String fileId) {
            this.fileId = fileId;
        }

        public String getMD5() {
            return MD5;
        }

        public void setMD5(String MD5) {
            this.MD5 = MD5;
        }

        public int getChunkIndex() {
            return chunkIndex;
        }

        public void setChunkIndex(int chunkIndex) {
            this.chunkIndex = chunkIndex;
        }

        public byte[] getData() {
            return data;
        }

        public void setData(byte[] data) {
            this.data = data;
        }
    }

    private class FileUploadProgress {
        // 已上传的分片索引集合
        private final Set<Integer> uploadedChunks = Collections.newSetFromMap(new ConcurrentHashMap<>());

        // 添加已上传的分片索引
        public void addUploadedChunk(int chunkIndex) {
            uploadedChunks.add(chunkIndex);
        }

        // 检查分片是否已上传
        public boolean isChunkUploaded(int chunkIndex) {
            return uploadedChunks.contains(chunkIndex);
        }

        // 获取分片索引集合的大小
        public int getUploadedChunksCount() {
            return uploadedChunks.size();
        }

        // 获取所有已上传的分片索引
        public Set<Integer> getUploadedChunks() {
            return uploadedChunks;
        }

    }


}
